﻿/**
 * MOTOR 2 - RAPID FLASH DYNAMICS
 * Copyright (c) 2007-2008, Michael Baczynski
 * Based on the Box2D Engine by Erin Catto, http://www.box2d.org
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the polygonal nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without specific
 *   prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package de.polygonal.motor2.collision.shapes.data {
	import de.gameduell.framework.debug.Debug;	
	import de.polygonal.motor2.collision.shapes.PolyShape;	
	import de.polygonal.motor2.collision.shapes.ShapeTypes;
	import de.polygonal.motor2.math.V2;	

	public class PolyData extends ShapeData {
		private var _vertexCount:int;
		private var _vertices:Array;
		private var _regular:Boolean;
		private var _radius:Number;
		private var _area:Number;

		public function PolyData(density:Number, vertices:Array) {
			super(density);
			setVertices(vertices);
		}

		public function isRegular():Boolean { 
			return _regular; 
		}

		public function get radius():Number {
			return _radius;
		}

		override public function get area():Number {
			return _area;
		}

		public function getVertexCount():int { 
			return _vertexCount; 
		}

		public function getVertices():Array { 
			var i:int, copy:Array;
			
			copy = new Array(_vertexCount);
			for (i = 0;i < _vertexCount; i++)
				copy[i] = new V2(_vertices[i].x, _vertices[i].y);
			return copy;
		}

		/*
		 * format: [xi, yi, ...], i = 1..n
		 */
		public function setVertices(source:Array):void {
			try {
				var i:int, j:int, k:int, rSq:Number;
				var v0:V2, v1:V2, v2:V2, n:V2;
				var chain:V2;
				
				if (k % 2 != 0)
					throw new Error("invalid source data");
				
				k = source.length / 2;
				_vertices = null;
				
				if (k < 3) throw new Error("invalid source data");
				
				chain = v0 = new V2(parseFloat(source[j]), parseFloat(source[int(j + 1)]));
				j += 2;
				
				if (isNaN(v0.x) || isNaN(v0.y))
					throw new Error("invalid source data");
				
				for (i = 1;i < k; i++) {
					v1 = new V2(parseFloat(source[j]), parseFloat(source[int(j + 1)]));
					j += 2;
					
					if (isNaN(v1.x) || isNaN(v1.y))
						throw new Error("invalid source data");
					
					v0.next = v1;
					v0 = v1;
				}
				v0.next = chain;
				
				/* check for overlapping vertices */
				v0 = chain;
				for (i = 0;i < k - 1; i++) {
					v1 = v0.next;
					for (j = i + 1;j < k; j++) {
						if ((v1.x - v0.x) * (v1.x - v0.x) + (v1.y - v0.y) * (v1.y - v0.y) < .1)
							throw new Error("overlapping vertices detected");
						v1 = v1.next;
					}
					v0 = v0.next;
				}
				
				/* check convexity, O(n^2) but never fails */
				var ex:Number, ey:Number;
				v0 = chain;
				v1 = v0.next;
				for (i = 0;i < k; i++) {
					ex = v1.x - v0.x;
					ey = v1.y - v0.y;
					
					n = v1.next;
					for (j = 0;j < k - 2; j++) {
						if ((ex * (n.y - v0.y) - (n.x - v0.x) * ey ) < 0)
							throw new Error("shape is not convex");
						n = n.next;
					}
					
					v0 = v1;
					v1 = v0.next;
				}
				
				/* check if vertices are clockwise ordered */
				v0 = chain;
				var a:Number;
				for (i = 0;i < k; i++) {
					v1 = v0.next;
					a += v0.x * v1.y - v0.y * v1.x;
					v0 = v1;
				}
				if (a < 0) throw new Error("vertices are not clockwise ordered");
				
				/* all tests passed, write into vertex list */
				_vertexCount = k;
				_vertices = new Array();
				for (i = 0, v0 = chain;i < k; i++) {
					_vertices[i] = v0;
					v0 = v0.next;
				}
				
				/* check if polygon is regular (ngon) */ 
				var e1x:Number, e1y:Number, e2x:Number, e2y:Number, t:Number;
				_regular = true;
				v0 = chain;
				v1 = v0.next;
				v2 = v1.next;
				
				e1x = v2.x - v1.x;
				e1y = v2.y - v1.y;
				e2x = v1.x - v0.x;
				e2y = v1.y - v0.y;
				a = Math.atan2(e1x * e2y - e1y * e2x, e1x * e2x + e1y * e2y);
				for (i = 1;i < k; i++) {
					v0 = v1;
					v1 = v2;
					v2 = v2.next;
					
					e1x = v2.x - v1.x;
					e1y = v2.y - v1.y;
					e2x = v1.x - v0.x;
					e2y = v1.y - v0.y;
					
					t = Math.atan2(e1x * e2y - e1y * e2x, e1x * e2x + e1y * e2y);
					if (Math.abs(a - t) > 1e-6) {
						_regular = false;
						break;
					}
				}
				
				/* compute bounding sphere radius */
				rSq = Number.MIN_VALUE;
				v0 = chain;
				for (i = 0, v0 = chain;i < k; i++) {
					rSq = Math.max(rSq, v0.x * v0.x + v0.y * v0.y);
					v0 = v0.next;
				}
				_radius = Math.sqrt(rSq);
				
				/* invalidate mass data (forces recomputation) */
				invalidate();
			}
			catch (e:Error) {
				trace("invalid polygon shape detected (" + e.message + ")");
			}
		}

		override public function getShapeClass():Class {
			return PolyShape;
		}

		override protected function computeMass():void {
			var i0:int, i1:int, v:V2;
			
			/* centroid, area, inertia */
			var cx:Number = 0;
			var cy:Number = 0;
			var A:Number = 0;
			var I:Number = 0;
			
			/* triangle vertices */
			var p1x:Number = 0, p2x:Number, p2y:Number;
			var p1y:Number = 0, p3x:Number, p3y:Number;
			
			/* triangle edges */
			var e1x:Number, e1y:Number;
			var e2x:Number, e2y:Number;
			
			var D:Number, triArea:Number;
			var intx2:Number, inty2:Number;
			var inv3:Number = 1 / 3;
			
			for (i0 = _vertexCount - 1, i1 = 0;i1 < _vertexCount; i0 = i1++) {
				v = _vertices[i0];
				p2x = v.x;
				p2y = v.y;
				
				v = _vertices[i1];
				p3x = v.x;
				p3y = v.y;
				
				e1x = p2x - p1x;
				e1y = p2y - p1y;
				
				e2x = p3x - p1x;
				e2y = p3y - p1y;
				
				//triangle area .5 * perpDot(e1, e2)
				D = (e1x * e2y - e1y * e2x);
				
				A += (triArea = .5 * D);
				
				//centroid
				cx += triArea * inv3 * (p1x + p2x + p3x);
				cy += triArea * inv3 * (p1y + p2y + p3y);
				
				intx2 = inv3 * (.25 * (e1x * e1x + e2x * e1x + e2x * e2x) + (p1x * e1x + p1x * e2x)) + .5 * p1x * p1x;
				inty2 = inv3 * (.25 * (e1y * e1y + e2y * e1y + e2y * e2y) + (p1y * e1y + p1y * e2y)) + .5 * p1y * p1y;
				
				//inertia
				I += D * (intx2 + inty2);
			}
			
			//inertia tensor relative to the center
			_I = _density * (I - A * (cx * cy + cy * cy));
			_mass = _density * A;
			_cm = new V2(cx * 1 / A, cy * 1 / A);
			_area = A;
		}

		override protected function setType():void {
			type = ShapeTypes.POLY;
		}
	}
}